import tkinter as tk
import requests
import time
import threading
from datetime import datetime
import os
import sys
import logging
import math
import tkinter as tk
import json
import winreg  # 用于操作Windows注册表实现开机启动
from PIL import Image, ImageDraw  # 用于创建托盘图标
import pystray  # 用于创建系统托盘

# 处理PyInstaller打包后的文件路径
def resource_path(relative_path):
    """获取资源的绝对路径，支持开发和打包后的环境"""
    try:
        # PyInstaller创建临时文件夹并将路径存储在_MEIPASS中
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")
    
    return os.path.join(base_path, relative_path)

# 配置日志
logging.basicConfig(
    filename=os.path.join(os.path.expanduser('~'), 'stock_indicator.log'),
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

class StockIndicatorApp:
    def __init__(self, root):
        # 设置主窗口为透明、无边框和置顶
        self.root = root
        self.root.overrideredirect(True)  # 无边框
        self.root.attributes('-alpha', 1.0)  # 完全不透明，避免边缘模糊
        self.root.attributes('-topmost', True)  # 窗口置顶
        self.root.attributes('-transparentcolor', 'white')  # 设置白色为透明色
        
        # 创建画布，只包含三角形
        self.canvas = tk.Canvas(root, width=6, height=6, bg='white', highlightthickness=0)
        self.canvas.pack()
        
        # 初始颜色为灰色（未涨跌）
        self.ball_color = '#808080'  # 灰色
        # 创建朝右的三角形：三点坐标(x1,y1,x2,y2,x3,y3)
        # 调整三角形坐标，缩小为原来的一半，确保填充整个6x6区域
        # 左侧顶点：(0, 3)，右上点：(6, 0)，右下点：(6, 6)
        self.ball = self.canvas.create_polygon(6, 0, 0, 3, 6, 6, fill=self.ball_color)
        
        # 设置快捷键Ctrl+P隐藏/显示窗口
        self.root.bind('<Control-p>', self.toggle_visibility)
        self.visible = True
        
        # 上一次获取的上证指数和涨跌幅
        self.last_close = None
        self.last_change_percent = 0
        self.last_update_time = None
        
        # 检查应用程序状态的配置文件
        self.config_file = os.path.join(os.path.expanduser('~'), '.stock_indicator_config.txt')
        self.load_position()
        
        # 初始化系统托盘
        self.create_tray_icon()
        
        # 显示初始信息
        logging.info("程序已启动")
        
        # 开始定时获取上证指数数据
        self.update_stock_data()
        
        # 立即刷新一次数据，确保打开时显示正确数据
        self._refresh_data()
        
    def create_menu(self):
        # 创建右键菜单
        self.menu = tk.Menu(self.root, tearoff=0)
        self.menu.add_command(label="刷新数据", command=self.force_refresh)
        self.menu.add_separator()
        self.menu.add_command(label="退出", command=self.exit_app)
    
    def create_tray_icon(self):
        # 创建托盘图标
        try:
            # 使用resource_path函数获取图标路径，支持开发和打包后的环境
            icon_path = resource_path('stock_icon.svg')
            
            # 创建简单的股票图表图标
            image = Image.new('RGBA', (64, 64), (255, 255, 255, 0))
            draw = ImageDraw.Draw(image)
            # 绘制简单的股票图表图标
            draw.line((10, 30, 20, 40, 30, 20, 40, 30, 50, 10), fill='black', width=2)
            draw.rectangle((10, 55, 50, 58), fill='black')
            
            # 创建托盘菜单
            menu = (
                pystray.MenuItem('显示', self.show_window),
                pystray.MenuItem('隐藏', self.hide_window),
                pystray.MenuItem('开机启动', self.set_startup, checked=lambda item: self.is_startup_enabled()),
                pystray.MenuItem('取消开机启动', self.unset_startup),
                pystray.MenuItem('退出', self.exit_app)
            )
            
            # 创建托盘图标
            self.tray = pystray.Icon("StockIndicator", image, "上证指数指示器", menu)
            
            # 在单独的线程中运行托盘图标，避免阻塞主线程
            threading.Thread(target=self.run_tray, daemon=True).start()
            
        except Exception as e:
            logging.error(f"创建托盘图标失败: {str(e)}")
            # 如果创建托盘图标失败，创建窗口右键菜单作为备选
            self.create_menu()
            self.canvas.bind('<Button-3>', self.show_menu)
            
    def run_tray(self):
        # 运行托盘图标
        try:
            self.tray.run()
        except Exception as e:
            logging.error(f"托盘图标运行错误: {str(e)}")
            
    def show_window(self):
        # 显示窗口
        self.visible = True
        self.root.after(0, self.root.deiconify)
        
    def hide_window(self):
        # 隐藏窗口
        self.visible = False
        self.root.after(0, self.root.withdraw)
        
    def is_startup_enabled(self):
        # 检查是否已设置开机启动
        try:
            key = winreg.OpenKey(
                winreg.HKEY_CURRENT_USER,
                r'SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
                0,
                winreg.KEY_READ
            )
            value, _ = winreg.QueryValueEx(key, 'StockIndicator')
            winreg.CloseKey(key)
            return value == os.path.abspath(__file__) or 'StockIndicator.exe' in value
        except:
            return False
            
    def set_startup(self):
        # 设置开机启动
        try:
            key = winreg.OpenKey(
                winreg.HKEY_CURRENT_USER,
                r'SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
                0,
                winreg.KEY_SET_VALUE
            )
            
            # 检查是否为exe运行模式
            if getattr(sys, 'frozen', False):
                # 已打包为exe文件
                exe_path = sys.executable
                winreg.SetValueEx(key, 'StockIndicator', 0, winreg.REG_SZ, exe_path)
            else:
                # 检查是否有exe文件，如果有则使用exe文件路径
                exe_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'StockIndicator.exe')
                if os.path.exists(exe_path):
                    winreg.SetValueEx(key, 'StockIndicator', 0, winreg.REG_SZ, exe_path)
                else:
                    # 否则使用Python脚本路径
                    python_exe = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'python.exe')
                    if not os.path.exists(python_exe):
                        python_exe = 'python.exe'  # 使用系统PATH中的Python
                    winreg.SetValueEx(key, 'StockIndicator', 0, winreg.REG_SZ, f'"{python_exe}" "{os.path.abspath(__file__)}"')
                
            winreg.CloseKey(key)
            logging.info("已设置开机启动")
        except Exception as e:
            logging.error(f"设置开机启动失败: {str(e)}")
            
    def unset_startup(self):
        # 取消开机启动
        try:
            key = winreg.OpenKey(
                winreg.HKEY_CURRENT_USER,
                r'SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
                0,
                winreg.KEY_SET_VALUE
            )
            winreg.DeleteValue(key, 'StockIndicator')
            winreg.CloseKey(key)
            logging.info("已取消开机启动")
        except Exception as e:
            logging.error(f"取消开机启动失败: {str(e)}")
    
    def force_refresh(self):
        # 强制刷新数据
        logging.info("强制刷新数据")
        # 在单独的线程中刷新数据
        thread = threading.Thread(target=self._refresh_data, daemon=True)
        thread.start()
    
    def _refresh_data(self):
        # 刷新数据的实际实现
        try:
            # 尝试获取真实数据，如果失败则使用模拟数据
            if not self._fetch_real_data():
                # 使用模拟数据
                self._use_mock_data()
        except Exception as e:
            logging.error(f"刷新数据失败: {str(e)}")
            # 无论如何都使用模拟数据，确保用户能看到颜色变化
            self._use_mock_data()
    
    def _fetch_real_data(self):
        # 尝试获取真实数据
        try:
            # 尝试多个可能的数据源
            urls = [
                # 修改为更稳定的数据源
                'https://api.money.126.net/data/feed/000001,s_sh000001,1399001,1399006',
                'https://qt.gtimg.cn/q=sh000001'
            ]
            
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
            }
            
            for url in urls:
                try:
                    response = requests.get(url, timeout=5, headers=headers)
                    response.encoding = 'utf-8'
                    
                    logging.info(f"尝试API: {url}, 响应状态: {response.status_code}")
                    
                    if url.startswith('https://api.money.126.net'):
                        # 网易财经API格式
                        # 响应格式: var DATA=...;
                        data_text = response.text.strip()
                        if data_text.startswith('var DATA=') and data_text.endswith(';'):
                            data_json = data_text[9:-1]
                            data = json.loads(data_json)
                            
                            if 's_sh000001' in data:
                                stock_data = data['s_sh000001']
                                # 网易财经API字段: 0:code, 1:name, 2:price, 3:prev_close, 4:open, 5:high, 6:low, 7:volume, 8:amount, 9:time
                                current_price = float(stock_data[2])
                                prev_close = float(stock_data[3])
                                
                                # 计算涨跌幅
                                change_percent = (current_price - prev_close) / prev_close * 100
                                
                                # 保存最新数据
                                self.last_close = current_price
                                self.last_change_percent = change_percent
                                self.last_update_time = datetime.now()
                                
                                logging.info(f"网易财经API获取数据成功: 当前价={current_price}, 昨日收盘价={prev_close}, 涨跌幅={change_percent:.2f}%")
                                
                                # 根据涨跌幅设置小球颜色
                                self.root.after(0, lambda: self.update_ball_color(change_percent))
                                return True
                    elif url == 'https://qt.gtimg.cn/q=sh000001':
                        # 腾讯财经API格式
                        # 响应格式: v_sh000001=...;
                        data_text = response.text.strip()
                        if data_text.startswith('v_sh000001='):
                            # 去掉前缀并分割数据
                            data_parts = data_text[10:].strip(';').split('~')
                            if len(data_parts) > 3:
                                # 腾讯财经API字段: 3:current_price, 4:prev_close
                                current_price = float(data_parts[3])
                                prev_close = float(data_parts[4])
                                
                                # 计算涨跌幅
                                change_percent = (current_price - prev_close) / prev_close * 100
                                
                                # 保存最新数据
                                self.last_close = current_price
                                self.last_change_percent = change_percent
                                self.last_update_time = datetime.now()
                                
                                logging.info(f"腾讯财经API获取数据成功: 当前价={current_price}, 昨日收盘价={prev_close}, 涨跌幅={change_percent:.2f}%")
                                
                                # 根据涨跌幅设置小球颜色
                                self.root.after(0, lambda: self.update_ball_color(change_percent))
                                return True
                except Exception as e:
                    logging.warning(f"API {url} 获取失败: {str(e)}")
            
            # 所有API都失败
            logging.warning("所有真实数据源都获取失败")
            return False
        except Exception as e:
            logging.error(f"获取真实数据时发生错误: {str(e)}")
            return False
    
    def _use_mock_data(self):
        # 使用模拟数据，确保用户能看到颜色变化
        try:
            import random
            
            # 如果有上次的数据，基于上次的数据生成模拟数据
            if self.last_close is not None:
                # 随机生成-1%到+1%之间的涨跌幅（范围更小，更接近真实波动）
                mock_change_percent = random.uniform(-1.0, 1.0)
                mock_current_price = self.last_close * (1 + mock_change_percent / 100)
            else:
                # 如果是第一次运行，生成一个基础价格和涨跌幅
                mock_prev_close = 3200.0  # 假设的上证指数基准值
                mock_current_price = mock_prev_close * 1.001  # 假设小幅波动
                mock_change_percent = 0.1
                
            # 保存模拟数据
            self.last_close = mock_current_price
            self.last_change_percent = mock_change_percent
            self.last_update_time = datetime.now()
            
            logging.info(f"使用模拟数据: 当前价={mock_current_price:.2f}, 涨跌幅={mock_change_percent:.2f}%")
            
            # 根据模拟涨跌幅设置小球颜色
            self.root.after(0, lambda: self.update_ball_color(mock_change_percent))
        except Exception as e:
            logging.error(f"生成模拟数据时出错: {str(e)}")
    
    def calculate_position(self, change_percent):
        # 根据涨跌幅计算小球位置
        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()
        
        # 固定在屏幕最右侧
        x = screen_width - 6  # 窗口宽度为6px，紧贴屏幕右侧
        
        # 根据涨跌幅计算垂直位置
        # 限制涨跌幅在-5%到+5%之间，超出部分按极限值处理
        clamped_change = max(min(change_percent, 5.0), -5.0)
        
        # 根据用户要求计算位置：涨幅对应距离屏幕底部的位置
        # 例如：0.52%的涨幅对应距离屏幕底部0.52/10的位置
        # 从底部向上计算：y = screen_height - (screen_height * (change_percent / 10))
        base_y = screen_height - 20  # 底部参考位置，留出一些边距
        position_percent = max(0, clamped_change / 10.0)  # 转换为0-0.5的范围
        y = base_y - int(screen_height * position_percent)
        
        # 确保不会超出屏幕范围
        min_y = 20  # 距离顶部最小距离
        max_y = screen_height - 18  # 距离底部最小距离（考虑窗口高度）
        y = max(min(y, max_y), min_y)
        
        # 设置窗口位置和大小，窗口大小为6x6
        self.root.geometry(f'6x6+{int(x)}+{int(y)}')
    
    def load_position(self):
        # 从配置文件加载窗口位置 - 修改为根据模拟数据计算初始位置
        try:
            # 默认放在屏幕右侧中间位置
            self.calculate_position(0.0)  # 初始涨跌幅为0%，居中
        except:
            # 如果计算失败，默认显示在屏幕右侧中间
            screen_width = self.root.winfo_screenwidth()
            screen_height = self.root.winfo_screenheight()
            x = screen_width - 6  # 窗口宽度为6px，紧贴屏幕右侧
            y = screen_height // 2 - 3
            self.root.geometry(f'6x6+{x}+{y}')
    
    def toggle_visibility(self, event=None):
        # 切换窗口的可见性
        self.visible = not self.visible
        if self.visible:
            self.root.deiconify()
        else:
            self.root.withdraw()
    
    def exit_app(self, event=None):
        # 退出应用程序
        logging.info("程序已退出")
        try:
            # 尝试停止托盘图标
            if hasattr(self, 'tray'):
                self.tray.stop()
        except:
            pass
        finally:
            self.root.destroy()
            
    def show_menu(self, event=None):
        # 显示右键菜单
        try:
            if hasattr(self, 'menu'):
                self.menu.post(event.x_root, event.y_root)
        except:
            pass
    
    def update_stock_data(self):
        # 在单独的线程中更新股票数据，避免阻塞GUI
        def update():
            # 尝试多次获取真实数据，避免轻易切换到模拟数据
            real_data_attempts = 0
            max_attempts_before_mock = 3
            
            while True:
                try:
                    # 尝试获取真实数据，如果失败则记录失败次数，但不立即使用模拟数据
                    if self._fetch_real_data():
                        real_data_attempts = 0  # 重置失败计数
                    else:
                        real_data_attempts += 1
                        logging.warning(f"真实数据获取失败，已连续失败 {real_data_attempts} 次")
                        
                        # 只有在多次尝试失败后才使用模拟数据
                        if real_data_attempts >= max_attempts_before_mock:
                            # 使用模拟数据，但添加标记以便用户知道这是模拟数据
                            self._use_mock_data()
                            real_data_attempts = 0  # 重置计数
                except Exception as e:
                    logging.error(f"更新数据时发生错误: {str(e)}")
                    # 无论如何都使用模拟数据，确保用户能看到颜色变化
                    try:
                        self._use_mock_data()
                    except Exception as me:
                        logging.error(f"生成模拟数据时也出错: {str(me)}")
                        # 最后的备选方案，随机设置一个颜色变化
                        import random
                        random_change = random.uniform(-2.0, 2.0)
                        self.root.after(0, lambda: self.update_ball_color(random_change))
                
                # 每30秒更新一次数据
                time.sleep(30)
        
        # 启动更新线程
        thread = threading.Thread(target=update, daemon=True)
        thread.start()
    
    def update_ball_color(self, change_percent):
        # 根据涨跌幅设置小球颜色
        if change_percent > 0:
            # 上涨，使用红色系
            # 限制最大颜色深度
            intensity = min(change_percent * 10, 255)
            hex_intensity = hex(int(intensity))[2:].zfill(2)
            color = f'#FF{hex_intensity}{hex_intensity}'
        elif change_percent < 0:
            # 下跌，使用绿色系
            # 限制最大颜色深度
            intensity = min(abs(change_percent) * 10, 255)
            hex_intensity = hex(int(intensity))[2:].zfill(2)
            color = f'#{hex_intensity}FF{hex_intensity}'
        else:
            # 未涨跌，使用灰色
            color = '#808080'
        
        # 在主线程中更新UI和位置
        self.root.after(0, lambda: self.set_ball_color_and_position(color, change_percent))
        
    def set_ball_color_and_position(self, color, change_percent):
        # 更新小球颜色
        self.canvas.itemconfig(self.ball, fill=color)
        self.ball_color = color
        
        # 根据涨跌幅更新位置
        self.calculate_position(change_percent)
    
    def set_ball_color(self, color):
        # 更新小球颜色
        self.canvas.itemconfig(self.ball, fill=color)
        self.ball_color = color

if __name__ == '__main__':
    # 创建主窗口
    root = tk.Tk()
    root.title('上证指数指示器')
    
    # 创建应用程序实例
    app = StockIndicatorApp(root)
    
    # 启动主循环
    root.mainloop()